/*
 * 
 * 
 * This file is part of Group Explorer.
 * Copyright 2005 Nathan Carter
 * 
 * Group Explorer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Group Explorer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Group Explorer; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linking Group Explorer statically or dynamically with other modules is
 * making a combined work based on Group Explorer.  Thus, the terms and
 * conditions of the GNU General Public License cover the whole combination.
 * 
 * In addition, as a special exception, the copyright holders of Group
 * Explorer give you permission to combine Group Explorer program with free
 * software programs or libraries that are released under the GNU LGPL and
 * with code included in the standard release of the Qt Undo/Redo Framework
 * under its standard license (or modified versions of such code, with
 * unchanged license). You may copy and distribute such a system following
 * the terms of the GNU GPL for Group Explorer and the licenses of the other
 * code concerned, provided that you include the source code of that other
 * code when and as the GNU GPL requires distribution of source code.
 * 
 * Note that people who make modified versions of Group Explorer are not
 * obligated to grant this special exception for their modified versions; it
 * is their choice whether to do so. The GNU General Public License gives
 * permission to release a modified version without this exception; this
 * exception also makes it possible to release a modified version which
 * carries forward this exception.
 * 
 * 
 * 
 */


#include "cyclegraph.h"
#include "sympanewidgets.h"
#include "gehelpwindow.h"
#include "gewhatsthis.h"
#include "ge.h"
#include <qlayout.h>
#include <qpixmap.h>
#include <q3scrollview.h>
#include <qpainter.h>
#include <q3simplerichtext.h>
#include <q3vbox.h>
#include <qpushbutton.h>
//Added by qt3to4:
#include <Q3HBoxLayout>
#include <Q3PtrList>
#include <Q3VBoxLayout>
#include <Q3MemArray>
#include <math.h>
#include <q3pointarray.h>
#include <qbitarray.h>
#include <q3dragobject.h>
#include <qapplication.h>
#include <qclipboard.h>
//Added by me to help porting:
#include <q3mimefactory.h>


CGLargeView::CGLargeView ( GEVLarge* container, QWidget* parent, const char* name )
    : GEVLargeWidget( container, parent, name ), computedForThis( 0 )
{
    Q3HBoxLayout* layout = new Q3HBoxLayout( this );
    Q3ScrollView* sv = new Q3ScrollView( this );
    layout->addWidget( sv );
    graph = new ScrollableLabel( 0 );
    sv->addChild( graph );

    connect( graph, SIGNAL(scroll(int,int)), sv, SLOT(scrollBy(int,int)) );
    //connect( graph, SIGNAL(startDrag(QPoint)), this, SLOT(graphStartDrag(QPoint)) );

    addProperty( "zoom", "100" );

    GEWhatsThis::setup( graph, "cg-view" );
}

/*
void CGLargeView::graphStartDrag ( QPoint )
{
    ( new QImageDrag( p.convertToImage(), this ) )->dragCopy();
}
*/

void CGLargeView::copyImage ()
{
    QApplication::clipboard()->setPixmap( p, QClipboard::Clipboard );
}

GEltList sortByNameSize ( GEltList sortMe, GroupFile* gf )
{
    GEltList result( sortMe.size() );
    uint appendAt = 0;
    int nameLen = 1;
    while ( appendAt < result.size() ) {
        for ( uint i = 0 ; i < sortMe.size() ; i++ )
            if ( gf->getRepresentation( sortMe[i] ).text.length() == nameLen )
                result[appendAt++] = sortMe[i];
        nameLen++;
    }
    return result;
}

void CGLargeView::dumpAsyCode ()
{
#ifdef DUMP_ASY_FEATURE
    GEltListList myCycles;
    Q3PointArray myEltPoints;
    Q3PointArray myArcPoints;
    QSize mySize;
    GEltList tmp( groupfile->group.order - 1 );
    int numPseudoPixels = 1000;
    int granularity = 5;
    for ( GElt g = 1 ; g < groupfile->group.order ; g++ ) tmp[g - 1] = g;
    computeEverything( groupfile->group, sortByNameSize( tmp, groupfile ),
                       numPseudoPixels, granularity,
                       myCycles, myEltPoints, myArcPoints, mySize );
    qDebug( "%s", QString( "\n"
                           "//-------------------------------------------------------\n"
                           "// Here begins text generated by Group Explorer %1\n"
                           "// on %2, the Asymptote code for the multiplication\n"
                           "// table for the group %3.\n"
                           "//-------------------------------------------------------\n"
                           "\n"    
                           "picture cycleGraph = new picture;\n"
                           "real nodeDiameter = 1.5cm;\n"
                           "" )    
                  .arg( GE_VERSION_STRING ).arg( QDateTime::currentDateTime().toString() )
                  .arg( groupfile->codeName() ).latin1() );
    qDebug( "// Arcs:\n"
            "// (to change pen, etc., go to end of this big long statement)\n"
            "draw( cycleGraph, scale(nodeDiameter/%d) * (", numPseudoPixels );
    QString bunchOfPoints;
    int pointsPerLine = 6;
    for ( int i = 0 ; i <= myArcPoints.count() ; i++ ) {
        if ( !( i % pointsPerLine ) ) {
            if ( i ) qDebug( "%s", bunchOfPoints.latin1() );
            bunchOfPoints = "      ";
        }
        if ( i ) bunchOfPoints += ".."; else bunchOfPoints += "  ";
        bunchOfPoints += QString( "(%1,%2)" )
                         .arg( myArcPoints[i%myArcPoints.size()].x() )
                         .arg( -myArcPoints[i%myArcPoints.size()].y() );
        if ( i == myArcPoints.count() )
            qDebug( "%s", bunchOfPoints.latin1() );
    }
    qDebug( "    ),\n"
            "    black );\n"
            "" );
    qDebug( "// Nodes:\n"
            "pair[] nodeLocs = new pair[] {" );
    for ( int i = 0 ; i < myEltPoints.size() ; i++ )
        qDebug( "%s", QString( "\t(%1,%2)*(nodeDiameter/%3), // %4" )
                      .arg( myEltPoints[i].x() ).arg( -myEltPoints[i].y() )
                      .arg( numPseudoPixels ).arg( i ).latin1() );
    qDebug( "\t};\n"
            "string[] nodeNames = new string[] {" );
    for ( int i = 0 ; i < myEltPoints.size() ; i++ )
        qDebug( "%s", QString( "\t\"$%1$\", // %2" )
                      .arg( groupfile->getRepresentation( i ).text ).arg( i ).latin1() );
    qDebug( "\t};\n"
            "for ( int i = 0 ; i < nodeLocs.length ; ++i ) {\n"
            "    filldraw( cycleGraph, shift(nodeLocs[i]) *\n"
            "                          scale(0.25*nodeDiameter) * unitcircle,\n"
            "              lightgray, black );\n"
            "    add( cycleGraph,\n"
            "         scale(1.0) * Label( nodeNames[i], nodeLocs[i] ) );\n"
            "}\n" );
    qDebug( "add( cycleGraph );\n" );
#endif
}

// the following value represents the fact that we want to warn the user if they're
// about to make a pixmap greater than 1000x1000 pixels:
uint CGLargeView::maximumEfficientSize = 750;
// it is needed in the following method...
void CGLargeView::set ( QString key, QString oldvalue, QString value, QString desc )
{
    if ( key == "zoom" ) {
        uint newval = value.toInt();
        // but now check to be sure it's not too large:
        uint size = QMAX( mySize.width(), mySize.height() );
        uint maxGoodZoom = 100 * maximumEfficientSize / size;
        if ( ( newval > maxGoodZoom ) && desc.isNull() ) {
            newval = maxGoodZoom;
            change( key, QString::number( newval ), desc );
        }
        zoa->setEnabled( readProperty( "zoom" ).toInt() > 2 );
        zia->setEnabled( readProperty( "zoom" ).toInt() < 337 );
    }
    GEVLargeWidget::set( key, oldvalue, value, desc );
}

void CGLargeView::groupFileChanged ( GroupFile* gf )
{
    GEVLargeWidget::groupFileChanged( gf );
    saveEverything( gf );
}

QPoint CGLargeView::elementLocationPercent ( GElt e )
{
    if ( ( (int)e < myEltPoints.size() ) && mySize.width() && mySize.height() ) {
        return QPoint( myEltPoints[e].x() * 100 / mySize.width(),
                       myEltPoints[e].y() * 100 / mySize.height() );
    } else {
        return QPoint( -1, -1 ); // member variables not set up--too early to tell
    }
}

void CGLargeView::updateContent ( QStringList changedKeys )
{
    updateHighlighting( changedKeys );
    // if this function got called at all, then changedKeys.count() > 0, and so
    // we need to redraw the main view of things.
    updatePixmapP();
}

double linearinterp ( double from, double to, double pct )
{
    // linear interpolation from 'from' to 'to,' pct% of the way along (0==from, 1==to)
    return ( 1 - pct ) * from + pct * to;
}

void mutate ( double x, double y, double a, double b, double pct, double& rx, double& ry )
{
    // rescales x,y by mapping it to polar coordinates, rescaling the theta parameter
    // to fit between 2*pi*a and 2*pi*b (0<=a,b<=1), and converting back to rect. coords.
    // uses pct as a gravitational factor towards r=0.5,theta=(a+b)/2, (0<=pct<=1).
    // puts the results in rx,ry.
    double r, th;
    r = sqrt( x * x + y * y ) / 2.0;
    th = 2 * ( a * M_PI + ( b - a ) * atan2( y, x ) );
    rx = linearinterp( r * cos( th ), 0.5 * cos( ( a + b ) * M_PI ), pct );
    ry = linearinterp( r * sin( th ), 0.5 * sin( ( a + b ) * M_PI ), pct );
}

double wiggle ( double param )
{
    // maps [0,1] to [0,1] continuously, increasingly, but not via (lambda (x) x);
    // rather through a useful wiggle...see cycle graph pictures...
    double result = ( cos( ( 1 - param ) * M_PI ) + 1 ) * 0.5;
    return result * result;
}

uint bestpower ( Group& g, GEltList& orbit, GElt elt )
{
    // what's the best power of elt to use when placing its orbit inside the given orbit?
    // we want <elt> to intersect orbit as soon as possible, so we want to generate <elt>
    // using a power of elt that a) will generate ALL of <elt>, and b) will intersect
    // the given orbit earliest.
    // first, we find the uniform smallest k such that for every n, elt^n^k is in orbit.
    uint k = 1;
    GElt tmp = elt;
    while ( orbit.find( tmp ) == -1 ) {
        k++;
        tmp = g.op( tmp, elt );
    }
    // now find the answer, knowing that I only need to examine the kth powers:
    uint o = g.orderOf( elt );
    int smallestIndex = orbit.size(); // max + 1
    uint result = 0;
    for ( uint i = 1 ; i < o ; i++ ) if ( gcd( i, o ) == 1 ) {
        int idx = orbit.find( g.pow( g.pow( elt, i ), k ) );
        if ( ( idx > -1 ) && ( idx < smallestIndex ) ) {
            smallestIndex = idx;
            result = i;
        }
    }
    return result;
}

void CGLargeView::computeEverything (
    Group& g, GEltList permutedElements, double distanceRequested, uint granularity,
    GEltListList& cycles,
    Q3PointArray& eltpositions, Q3PointArray& arcpositions, QSize& size )
{
    // definition of parameters:
    // g == group to compute cycle graph for
    // permutedElements == elements { 1, 2, ..., g.order-1 } reordered to place first
    //   those that one would most like to start orbits.  E.g. generators, or shortest-
    //   named elements might like to be first to make the most readable cycle graphs.
    // distanceRequested == shortest distance that must exist between two nodes in the
    //   resulting cycle graph
    // granularity == the number of subdivisions for each arc (path) connecting 2 nodes
    // cycles == empty to start, gets filled with partitioned list of orbits (partitioned
    //   by placing empty lists as separators)
    // eltpositions == empty to start, gets filled with locations for graph nodes
    // arcpositions == empty to start, gets filled with a set of points which, when
    //   connected via a long path, draws all arcs connecting all nodes (subdivided)
    // size == empty to start, returns width and height of resulting graph.  that is,
    //   the maximum width & height spanned between any 2 points in eltpositions.  note
    //   also that eltpositions are adjusted so that the leftmost has x==0 and the
    //   topmost has y==0.  so you can just setWindow(0,0,size.width(),size.height())
    //   and use the values given in eltpositions and arcpositions without adjustment.

    Q3MemArray<uint> sizes;
    Q3MemArray<double> angles;
    Q3MemArray<double> eltxs;
    Q3MemArray<double> eltys;
    Q3MemArray<double> arcxs;
    Q3MemArray<double> arcys;
    Q3MemArray<double> peakxs;
    Q3MemArray<double> peakys;
    double shortestDistance;

    //
    // compute cycles (orbits)
    //

    cycles.clear();
    // check marks for each element in permutedElements; only identity starts checked:
    QBitArray checks( g.order );
    checks.fill( FALSE );
    checks.setBit( 0 );
    // begin main loop for creating list of orbits, ordered smallest to least:
    uint maxorder;
    GElt maxelt;
    do {
        // find non-checked element of maximal order
        maxorder = 0;
        maxelt = 0;
        for ( uint i = 0 ; i < permutedElements.size() ; i++ ) {
            if ( !checks.testBit( permutedElements[i] ) ) {
                uint ord = g.orderOf( permutedElements[i] );
                if ( ord > maxorder ) {
                    maxorder = ord;
                    maxelt = permutedElements[i];
                }
            }
        }
        // if we found such an element, then let's add its orbit to our result:
        if ( maxelt ) {
            GEltList orbit = g.orbitOf( maxelt, FALSE );
            for ( uint i = 0 ; i < orbit.size() ; i++ ) checks.setBit( orbit[i] );
            cycles.append( orbit );
        }
    } while ( maxelt );

    //
    // partition those cycles by the transitive closure of the "has an element in common"
    // relation:
    //

    GEltListList newcycles;
    if ( cycles.count() ) {
        newcycles.append( cycles[0] );
        cycles.remove( cycles.at( 0 ) );
        int i = 0;
        while ( i < newcycles.count() ) {
            int j = 0;
            while ( j < cycles.count() ) {
                // anything in common?
                bool incommon = FALSE;
                for ( uint k1 = 0 ; k1 < newcycles[i].size() ; k1++ ) {
                    for ( uint k2 = 0 ; k2 < cycles[j].size() ; k2++ ) {
                        if ( newcycles[i][k1] == cycles[j][k2] ) {
                            incommon = TRUE;
                            cycles[j] = g.orbitOf( g.pow( cycles[j][0], bestpower(
                                g, newcycles[i], cycles[j][0] ) ), FALSE );
                            break;
                        }
                    }
                    if ( incommon ) break;
                }
                // if so, move cycles[j] to end of newcycles array:
                if ( incommon ) {
                    newcycles.append( cycles[j] );
                    cycles.remove( cycles.at( j ) );
                } else {
                    j++;
                }
            }
            i++;
            if ( i >= newcycles.count() ) {
                if ( newcycles.count() ) {
                    newcycles.append( GEltList() );
                    i++;
                }
                if ( cycles.count() ) {
                    newcycles.append( cycles[0] );
                    cycles.remove( cycles.at( 0 ) );
                }
            }
        }
    }
    cycles = newcycles;

    //
    // compute the sizes of each partition (weights for space-elbowing) and the angles
    // they therefore get to span:
    //

    // first give sizes proportional to total number of elements in the partition:
    sizes.resize( 0 );
    int last = 0;
    uint lgsize = 0;
    for ( int i = 0 ; i < cycles.count() ; i++ ) {
        if ( cycles[i].size() ) {
            if ( last == i ) {
                sizes.resize( sizes.size() + 1 );
                sizes[sizes.size() - 1] = 0;
            }
            sizes[sizes.size() - 1] += cycles[i].size();
        } else {
            last = i + 1;
        }
    }
    for ( uint i = 0 ; i < sizes.size() ; i++ )
        if ( lgsize < sizes[i] ) lgsize = sizes[i];
    // now give angles that give elbow-room proportional to size, except don't let anyone
    // get larger than 50% of the whole (180 degrees):
    angles.fill( 0.0, sizes.size() );
    double sum = 0.0;
    if ( angles.size() == 1 ) {
        angles[0] = 0.5;
    } else {
        for ( uint i = 0 ; i < angles.size() ; i++ )
            sum += ( angles[i] = double( sizes[i] ) );
        for ( uint i = 0 ; i < angles.size() ; i++ ) angles[i] /= sum;
        for ( uint i = 0 ; i < angles.size() ; i++ ) if ( angles[i] > 0.5 ) {
            for ( uint j = 0 ; j < angles.count() ; j++ ) if ( i != j )
                angles[j] *= 0.5 / ( 1 - angles[i] );
            angles[i] = 0.5;
            break;
        }
    }
    // now make the angles running totals:
    sum = 0.0;
    for ( uint i = 0 ; i < angles.size() ; i++ ) angles[i] = ( sum += angles[i] );
    // want to rotate this aesthetically, but that's a job for later
    if ( angles.size() > 1 ) {
        // find largest sequence
        uint seqstart = 0;
        uint seqend = 0;
        for ( uint i = 0 ; i < sizes.size() ; i++ ) {
            if ( sizes[i] > sizes[seqstart] ) {
                seqstart = i;
                seqend = 0;
            } else if ( !seqend && ( sizes[i] < sizes[seqstart] ) ) {
                seqend = i;
            }
        }
        seqstart = ( seqstart + sizes.size() - 1 ) % sizes.size();
        seqend = ( seqend + sizes.size() - 1 ) % sizes.size();
        double delta = angles[seqend] + angles[seqstart];
        while ( delta > 1.0 ) delta -= 1.0;
        delta = delta * 0.5 - 0.25;
        for ( uint i = 0 ; i < angles.size() ; i++ ) {
            angles[i] -= delta;
            while ( angles[i] < 0.0 ) angles[i] += 1.0;
            while ( angles[i] > 1.0 ) angles[i] -= 1.0;
        }
    }

    //
    // placing the nodes of the cycle graph in [-1,1]^2 subset of R^2:
    //

    eltxs.fill( 0.0, g.order );
    eltys.fill( 0.0, g.order );
    arcxs.resize( 0 );
    arcys.resize( 0 );
    Q3MemArray<int> ring; // what ring is each element in?  (0 == outermost, etc. inward)
    ring.fill( -1, g.order );
    ring[0] = 0;
    int length = -1;
    int step = 0; // step counts through the length # of things in each partition
    uint eqcl = 0; // which equivalence class in the partition am I in?
    double a = ( angles.size() > 1 ) ? angles[angles.size() - 1] : 0; // start angle
    double b = angles.size() ? angles[0] : 0; // stop angle (.?.:. is for trivial group)
    peakxs.resize( cycles.count() );
    peakys.resize( cycles.count() );
    for ( int i = 0 ; i < cycles.count() ; i++ ) {
        if ( a > b ) a -= 1.0;
        // record the peak of the arc for later positioning of the graph:
        double pct = double( step ) / double( length );
        double radius = sqrt( QMAX( double( sizes[eqcl] ) / double( lgsize ), 0.25 ) );
        peakxs[i] = radius * cos( M_PI * ( a + b ) );
        peakys[i] = radius * sin( M_PI * ( a + b ) );
        if ( cycles[i].size() ) {
            // determine number of orbits in this partition:
            if ( length == -1 ) {
                for ( int j = i ; j < cycles.count() ; j++ ) {
                    if ( !cycles[j].size() ) {
                        length = j - i;
                        break;
                    }
                }
            }
            // now place each orbit, careful to ensure stepping inside when needed,
            // also careful not to overwrite earlier things:
            GElt e = 0;
            GElt lastE = 0;
            for ( uint j = 0 ; j <= cycles[i].size() ; j++ ) {
                if ( j < cycles[i].size() ) {
                    e = cycles[i][j];
                    if ( ( eltxs[e] == 0.0 ) && ( eltys[e] == 0.0 ) ) {
                        // first determine where the point belongs if on the outer ring
                        double t = 2 * M_PI *
                            ( double( j + 1 ) / double( cycles[i].size() + 1 ) - 0.25 );
                        mutate( cos( t ) * radius, ( sin( t ) + 1 ) * radius,
                                a, b, pct, eltxs[e], eltys[e] );
                        ring[e] = step;
                    }
                } else
                    e = 0;
                // now draw the path from lastE to e in the arcs array:
                uint base = arcxs.size();
                arcxs.resize( arcxs.size() + granularity );
                arcys.resize( arcys.size() + granularity );
                for ( uint k = 0 ; k < granularity ; k++ ) {
                    double p = double( k ) / double( granularity - 1 );
                    double t = 2 * M_PI *
                        ( ( j + p ) / double( cycles[i].size() + 1 ) - 0.25 );
                    double x1, y1, x2, y2;
                    mutate( cos( t ) * radius, ( sin( t ) + 1 ) * radius,
                            a, b, double( ring[lastE] ) / double( length ), x1, y1 );
                    mutate( cos( t ) * radius, ( sin( t ) + 1 ) * radius,
                            a, b, double( ring[e] ) / double( length ), x2, y2 );
                    double wig;
                    if ( ring[lastE] < ring[e] )
                        wig = 1 - wiggle( 1 - p );
                    else
                        wig = wiggle( p );
                    arcxs[base + k] = linearinterp( x1, x2, wig );
                    arcys[base + k] = linearinterp( y1, y2, wig );
                }
                // and update lastE:
                lastE = e;
            }
            step++;
        } else {
            length = -1;
            step = 0;
            if ( ++eqcl < angles.size() ) {
                a = b;
                b = angles[eqcl];
            }
        }
    }

    //
    // placing the nodes of the cycle graph in [-1,1]^2 subset of R^2:
    //

    shortestDistance = 10; // much larger than the distance btwn any 2 points in [-1,1]^2
    double dist;
    for ( uint i = 0 ; i < eltxs.size() ; i++ ) {
        for ( uint j = 0 ; j < i ; j++ ) {
            dist = sqrt( pow( eltxs[i] - eltxs[j], 2 ) + pow( eltys[i] - eltys[j], 2 ) );
            if ( dist < shortestDistance ) shortestDistance = dist;
        }
    }

    //
    // compute new positions based on requirement of shortest distance given,
    // and compute final diagram size:
    //

    QPoint smallest( 0, 0 );
    QPoint largest( 0, 0 );
    eltpositions.fill( QPoint( 0, 0 ), eltxs.size() );
    arcpositions.fill( QPoint( 0, 0 ), arcxs.size() );
    if ( shortestDistance > 0.0 ) {
        double factor = distanceRequested / shortestDistance;
        for ( uint i = 0 ; i < eltxs.size() ; i++ ) {
            eltpositions[i] = QPoint( int( eltxs[i] * factor ), int( eltys[i] * factor ) );
            if ( eltpositions[i].x() < smallest.x() ) smallest.setX( eltpositions[i].x() );
            if ( eltpositions[i].y() < smallest.y() ) smallest.setY( eltpositions[i].y() );
            if ( eltpositions[i].x() > largest.x() ) largest.setX( eltpositions[i].x() );
            if ( eltpositions[i].y() > largest.y() ) largest.setY( eltpositions[i].y() );
        }
        for ( uint i = 0 ; i < peakxs.size() ; i++ ) {
            QPoint tmp = QPoint( int( peakxs[i] * factor ), int( peakys[i] * factor ) );
            if ( tmp.x() < smallest.x() ) smallest.setX( tmp.x() );
            if ( tmp.y() < smallest.y() ) smallest.setY( tmp.y() );
            if ( tmp.x() > largest.x() ) largest.setX( tmp.x() );
            if ( tmp.y() > largest.y() ) largest.setY( tmp.y() );
        }
        for ( uint i = 0 ; i < arcxs.size() ; i++ )
            arcpositions[i] = QPoint( int( arcxs[i] * factor ), int( arcys[i] * factor ) );
        smallest -= QPoint( int( distanceRequested ), int( distanceRequested ) );
        largest += QPoint( int( distanceRequested ), int( distanceRequested ) );
        for ( uint i = 0 ; i < eltxs.size() ; i++ ) eltpositions[i] -= smallest;
        for ( uint i = 0 ; i < arcxs.size() ; i++ ) arcpositions[i] -= smallest;
    }
    size = QSize( largest.x() - smallest.x(), largest.y() - smallest.y() );

}

void CGLargeView::saveEverything ( GroupFile* gf )
{
    if ( gf == computedForThis ) return; // done this already
    // otherwise, we do all the computations:
    computedForThis = gf;
    QSize largest = largestElementText();
    int largestSide = QMAX( largest.width(), largest.height() );
    GEltList tmp( gf->group.order - 1 );
    for ( GElt g = 1 ; g < gf->group.order ; g++ ) tmp[g - 1] = g;
    computeEverything( gf->group, sortByNameSize( tmp, gf ),
                       1.5 * QMAX( largestSide, 30 ), 15,
                       myCycles, myEltPoints, myArcPoints, mySize );
}

void CGLargeView::updatePixmapP ()
{
    // begin by calculating the size of the pixmap, including sizes for coset separators
    int zoom = readProperty( "zoom" ).toInt();
    // now resize the pixmap and prepare to paint on it (this is where zoom is applied)
    p.resize( ( mySize * zoom ) / 100 );
    QPainter paint( &p );
    // clear the background:
    paint.setPen( Qt::NoPen );
    paint.setBrush( QColor( 200, 200, 240, QColor::Rgb ) );
    paint.drawRect( 0, 0, p.width(), p.height() );
    // prepare for drawing:
    paint.setWindow( 0, 0, mySize.width(), mySize.height() );
    QSize largest = largestElementText();
    int largestSide = QMAX( largest.width(), largest.height() ) + 3;
    bool anyBackgroundUsed = highlightingTypeUsed( 0 );
    // now draw the lines connecting the nodes in the cycle graph
    paint.setPen( Qt::black );
    for ( int i = 1 ; i <= myArcPoints.count() ; i++ )
        paint.drawLine( myArcPoints[i - 1], myArcPoints[i % myArcPoints.size()] );
    // and lastly place the nodes atop the lines
    for ( int i = 0 ; i < myEltPoints.size() ; i++ ) {
        if ( isElementHighlightedBy( i, 1 ) ) {
            paint.setBrush( elementHighlighting( i, 1 ) );
            paint.drawEllipse( myEltPoints[i].x() - largestSide / 2 - 6,
                               myEltPoints[i].y() - largestSide / 2 - 6,
                               largestSide + 12, largestSide + 12 );
        }
        paint.setPen( Qt::black );
        if ( !anyBackgroundUsed ) {
            paint.setBrush( Qt::darkCyan );
        } else if ( isElementHighlightedBy( i, 0 ) ) {
            paint.setBrush( elementHighlighting( i, 0 ) );
        } else {
            paint.setBrush( colorGroup().background() );
        }
        paint.drawEllipse( myEltPoints[i].x() - largestSide / 2,
                           myEltPoints[i].y() - largestSide / 2,
                           largestSide, largestSide );
        if ( isElementHighlightedBy( i, 2 ) ) {
            paint.save();
            paint.setBrush( elementHighlighting( i, 2 ) );
            paint.setClipRect( myEltPoints[i].x() - largestSide / 2,
                               myEltPoints[i].y() - largestSide / 2,
                               largestSide, largestSide / 3, Qt::ReplaceClip );
            paint.drawEllipse( myEltPoints[i].x() - largestSide / 2,
                               myEltPoints[i].y() - largestSide / 2,
                               largestSide, largestSide );
            paint.restore();
            paint.drawLine( myEltPoints[i].x() - int( 0.527046 * largestSide ) + 2,
                            myEltPoints[i].y() - largestSide / 6 - 1,
                            myEltPoints[i].x() + int( 0.527046 * largestSide ) - 2,
                            myEltPoints[i].y() - largestSide / 6 - 1 );
        }
        drawElementText( i, &paint, myEltPoints[i], Qt::AlignCenter );
    }
    // make that pixmap what we show:
    graph->setPixmap( p );
    graph->resize( p.size() );
}

QPixmap CGLargeView::quickPixmap ( int w, int h, GroupFile* gf ) {
    // grab all needed data:
    GEltListList cycles;
    Q3PointArray eltpoints;
    Q3PointArray arcpoints;
    QSize size;
    GEltList tmp( gf->group.order - 1 );
    for ( GElt g = 1 ; g < gf->group.order ; g++ ) tmp[g - 1] = g;
    computeEverything( gf->group, sortByNameSize( tmp, gf ), 25.0, 5,
                       cycles, eltpoints, arcpoints, size );
    // if the user did not pass dimension parameters, choose a sensible default:
    if ( ( w < 0 ) || ( h < 0 ) ) {
        w = QMIN( QMAX( 30, size.width() ), QMAX( 30, int( gf->group.order ) ) );
        h = QMIN( QMAX( 30, size.height() ), QMAX( 30, int( gf->group.order ) ) );
    }
    QPixmap* p = new QPixmap( w, h );
    QPainter paint( p );
    // clear background:
    paint.setPen( Qt::NoPen );
    paint.setBrush( QColor( 200, 200, 240, QColor::Rgb ) );
    paint.drawRect( 0, 0, w, h );
    // draw cycles:
    paint.setWindow( 0, 0, size.width(), size.height() );
    QPen pen( Qt::black );
#ifdef Q_WS_MACX
    pen.setWidthF( 0.25 );
#endif
    paint.setPen( pen );
    paint.setBrush( Qt::darkCyan );
    for ( int i = 1 ; i <= arcpoints.count() ; i++ )
        paint.drawLine( arcpoints[i - 1], arcpoints[i % arcpoints.size()] );
    for ( int i = 0 ; i < eltpoints.count() ; i++ )
        paint.drawEllipse( eltpoints[i].x() - 7, eltpoints[i].y() - 7, 14, 14 );
    return *p;
}

QPixmap CGLargeView::currentPixmap ()
{
    return p;
}

Q3PtrList<QAction> CGLargeView::actionsToExport ()
{
    Q3PtrList<QAction> result;
    
    zia = new QAction( QIcon( qPixmapFromMimeSource( "zoomin.png" ) ), "Zoom in", this );
    zia->setShortcut( QKeySequence( "Ctrl++" ) );
    connect( zia, SIGNAL(activated()), this, SLOT(zoomIn()) );
    result.append( zia );
    
    zoa = new QAction( QIcon( qPixmapFromMimeSource( "zoomout.png" ) ), "Zoom out", this );
    zia->setShortcut( QKeySequence( "Ctrl+-" ) );
    connect( zoa, SIGNAL(activated()), this, SLOT(zoomOut()) );
    result.append( zoa );

    QAction* cpa = new QAction( QIcon( qPixmapFromMimeSource( "editcopy.png" ) ),
                                "Copy", this );
    cpa->setShortcut( QKeySequence( "Ctrl+C" ) );
    connect( cpa, SIGNAL(activated()), this, SLOT(copyImage()) );
    result.append( cpa );

#ifdef DUMP_ASY_FEATURE
    QAction* tmp = new QAction( QIcon( qPixmapFromMimeSource( "filesaveimage.png" ) ),
                                "Dump .asy code", this );
    tmp->setShortcut( QKeySequence( "Ctrl+D" ) );
    connect( tmp, SIGNAL(activated()), this, SLOT(dumpAsyCode()) );
    result.append( tmp );
#endif
    
    return result;
}

void CGLargeView::zoomIn ()
{
    uint zoom = readProperty( "zoom" ).toInt();
    zoom = zoom * 3 / 2;
    // cap it at three zooms in (337.5 = 100 * 3/2 * 3/2 * 3/2):
    if ( zoom > 337 ) zoom = 337;
    // fix the rounding error every time we come near 100:
    if ( ( 70 < zoom ) && ( zoom < 125 ) ) zoom = 100;
    change( "zoom", QString::number( zoom ),
            QString( "zoom in from %1% to %2%" )
            .arg( readProperty( "zoom" ) ).arg( zoom ) );
    // in the next line, we reread from mycopy because change() may be thwarted:
}

void CGLargeView::zoomOut ()
{
    uint zoom = readProperty( "zoom" ).toInt();
    zoom = zoom * 2 / 3;
    // cap it at 2 percent, because otherwise rounding would never let you zoom out!:
    if ( zoom < 2 ) zoom = 2;
    // fix the rounding error every time we come near 100:
    if ( ( 70 < zoom ) && ( zoom < 125 ) ) zoom = 100;
    change( "zoom", QString::number( zoom ),
            QString( "zoom out from %1% to %2%" )
            .arg( readProperty( "zoom" ) ).arg( zoom ) );
    // in the last line below, reread from mycopy because change() may be thwarted:
}


CGLargeControl::CGLargeControl ( GEVLarge* container, QWidget* parent, const char* name )
    : GEVLargeWidget( container, parent, name )
{
    Q3VBoxLayout* mainLayout = new Q3VBoxLayout( this, 0, 6 );

    // set up the tab page with the subset list box
    listBox = new SubsetListBox( container, 0, this );
    listBox->setHighlightTypes( QStringList()
        << "img/hightype-circle-background.jpg:Background"
        << "img/hightype-circle-border.jpg:Border"
        << "img/hightype-circle-top.jpg:Top" );
    if ( container ) container->addLargeWidget( listBox, TRUE );
    mainLayout->addWidget( listBox );

    // set up the Reset and Help buttons on the bottom
    Q3HBoxLayout* buttonLayout = new Q3HBoxLayout( mainLayout );

    QPushButton* helpButton = new QPushButton( "Help", this );
    buttonLayout->addWidget( helpButton );
    connect( helpButton, SIGNAL(clicked()), this, SLOT(showHelp()) );

    QPushButton* resetButton = new QPushButton( "Reset", this );
    buttonLayout->addWidget( resetButton );
    connect( resetButton, SIGNAL(clicked()), this, SIGNAL(reset()) );

    GEWhatsThis::setup( helpButton, "largeWindow-helpButton" );
    GEWhatsThis::setup( resetButton, "largeWindow-resetButton" );
}

void CGLargeControl::showHelp ()
{
    GEHelpWindow::showHelp( "rf-um-cg-options.html" );
}

void CGLargeControl::groupFileChanged ( GroupFile* gf )
{
    GEVLargeWidget::groupFileChanged( gf );
    listBox->setGroupFile( groupfile );
}


CGLarge::CGLarge ( QWidget* parent, const char* name )
    : GEVLarge( parent, name )
{
    addLargeWidget( view = new CGLargeView( this, splitter ), TRUE );
    addLargeWidget( new CGLargeControl( this, splitter ), FALSE );
}

QSize CGLarge::sizeHint () const
{
    return QSize( 600, 400 );
}

QPixmap CGLarge::currentPixmap ()
{
    return view->currentPixmap();
}

QPoint CGLarge::elementLocationPercent ( GElt e )
{
    return view->elementLocationPercent( e );
}


CGVisualizer::CGVisualizer ( GroupFile* gf )
    : GEVisualizer( gf )
{
}

QString CGVisualizer::typeName ()
{
    return "CycleGraph";
}

void CGVisualizer::createLarge ()
{
    large = new CGLarge();
}

void CGVisualizer::createSmall ()
{
    smallv = new CGSmall();
}

QPixmap CGVisualizer::quickPixmap ( int w, int h, int )
{
    return CGLargeView::quickPixmap( w, h, groupfile );
}


CGSmall::CGSmall ()
{
}

Q3PtrList<QAction> CGSmall::actionsToExport ()
{
    return Q3PtrList<QAction>(); // change me later
}


